# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

import copy
import gobject
import gtk
import os

from gettext import _expand_lang, GNUTranslations, NullTranslations
from pkg_resources import resource_stream, resource_exists, resource_listdir

(
        E_PANEL_TYPE_PLAYLIST,
        E_PANEL_TYPE_EQU,
        E_PANEL_TYPE_EFFECT,
        E_PANEL_TYPE_EFFECT_MISC,
        E_PANEL_TYPE_INFO,
        E_PANEL_TYPE_MEDIALIB,
        E_PANEL_TYPE_COLLECTIONS,
        E_PANEL_TYPE_OTHER,
        E_COUNT_PANEL_TYPE,
        ) = xrange(9)

def install_gettext(package, domain = 'etude', localedir=None, languages = None, 
        unicode = True, fallback = True, codeset = None, names = []):
    """
    install _() function pointing to gettext or ugettext if unicode is set

    package is the package in the given egg plugin

    The recommended final directory structure of a plugin with translations should be:
    plugin_dir/
        |- plugin_name/
        |   |- locale/
        |   |   |- lang1/
        |   |   |   `- domain.mo
        |   |   `- lang2/
        |   |       `- domain.mo
        |   |- __init__.py
        |   `- *.py
        `-setup.py
    in this case:
        import etude.plugins
        etude.plugins.install_gettext(__package__, domain = 'mydomain')
    For setup.py examples see builtin plugins.
    In this case in every other .py file in your plugin which uses translations
    you should put sthing like this: from . import _
    """
    if not package:
        return
    egg_module = __import__(package, globals(), locals(), [], -1)
    trans = egg_translation(package, domain, localedir, languages, fallback, codeset)
    egg_module.__dict__['_'] = unicode and trans.ugettext or trans.gettext
    if hasattr(names, "__contains__"):
        if "gettext" in names:
            egg_module.__dict__['gettext'] = egg_module.__dict__['_']
        if "ngettext" in names:
            egg_module.__dict__['ngettext'] = (unicode and self.ungettext
                                                         or self.ngettext)
        if "lgettext" in names:
            egg_module.__dict__['lgettext'] = self.lgettext
        if "lngettext" in names:
            egg_module.__dict__['lngettext'] = self.lngettext



def egg_translation(package, domain = 'etude', localedir = None, languages = None, 
        fallback = True, codeset = None):
    """
    used by install_gettext,
    creates from the found *.mo files GNUTranslation classes, which can be used for
    tranlations
    """
    if localedir == None:
        localedir = 'locale'
    mofile_fds = egg_find_translations(package, domain, localedir, languages, all=1)
    if not mofile_fds:
        if fallback:
            return NullTranslations()
        raise IOError(ENOENT, 'No translation file found for package %s domain %s' % 
                (package, domain))
    result = None
    for mofile_fd in mofile_fds:
        t = GNUTranslations(mofile_fd)
        # Copy the translation object to allow setting fallbacks and
        # output charset. All other instance data is shared with the
        # cached object.
        t = copy.copy(t)
        if codeset:
            t.set_output_charset(codeset)
        if result is None:
            result = t
        else:
            result.add_fallback(t)
    return result

def egg_find_translations(package, domain = 'etude', localedir = None, 
        languages = None, all = 0):
    """
    Finds translation files in the given package of a plugin egg.
    The location should usually be locale. For directory structure and examples see
    plugins setup.py and __init__.py
    """
    if localedir == None:
        localedir = 'locale'
    if languages is None:
        languages = []
        for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
            val = os.environ.get(envar)
            if val:
                languages = val.split(':')
                break
        if 'C' not in languages:
            languages.append('C')
    # now normalize and expand the languages
    nelangs = []
    for lang in languages:
        for nelang in _expand_lang(lang):
            if nelang not in nelangs:
                nelangs.append(nelang)
    # select a language
    if all:
        result = []
    else:
        result = None
    for lang in nelangs:
        if lang == 'C':
            break
        mofile = '/'.join((localedir, lang, '%s.mo' % domain))
        if resource_exists(package, mofile):
            mofile_fd = resource_stream(package, mofile)
            if all:
                result.append(mofile_fd)
            else:
                return mofile_fd
    return result



class Plugin(object):

    """
    Abstract class, don't subclass this
    """

    _short_name = "short_name" #should be the same across versions
    #short name is considered unique over plugins
    _show_name = None # the name which is shown in the list - should be translatable
    _short_descr = None # short descr of the plugin - should be translatable
    _version = "0.0"
    _unique = "short_name-0.0" # short name + version is enough, just to identify loaded
    #ASCII chars from 33 - 126
    _config = {}
    __author__ = ""
    __license__ = "LGPL"
    #each interface is a function, which can be called from anywhere
    #in the application, the functions should have one parameter
    _interfaces = {}
    _ui = "" # ui description to be merged in uimanager
    _actions = []


    def __init__(self, control):
        self.__signal_handlers = []
        self.control = control
        self.config = control.config
        self.config.update_defaults(self._unique, self._config)
        self._ui_merge_id = 0
        ui = self._ui
        if self._actions != []:
            self._actiongroup = gtk.ActionGroup(self._short_name+" shortcuts")
            self._actiongroup.add_actions(self._actions)
            self.control.uimanager.insert_action_group(self._actiongroup)
            if self._ui == "":
                ui = "<ui></ui>"
            strs = ["<accelerator action=\""+action[0]+"\" name=\"acc_"+action[0]+"\"/>"
                    for action in self._actions]
            ui = ui.replace('</ui>', ''.join(strs)+'</ui>')
        if ui != "":
            self._ui_merge_id = control.uimanager.add_ui_from_string(ui)

    def get_config(self, option):
        """
        get an option associated with the current plugin
        """
        return self.config.get(self._unique, option)

    def set_config(self, option, value):
        """
        set an option associated with the current plugin
        """
        return self.config.set(self._unique, option, value)

    def unload(self):
        """
        always call this function even if you overwrite it
        """
        for emitter, hid in self.__signal_handlers:
            emitter.disconnect(hid)
        self.__signal_handlers = []
        if self._ui_merge_id:
            self.control.uimanager.remove_ui(self._ui_merge_id)
        if hasattr(self, '_actiongroup'):
            try:
                self.control.uimanager.remove_action_group(self._actiongroup)
            except:
                pass

    def bind(self, emitter, signal, handler, *args):
        """
        use this function to connect outside of your plugin, for example
        to a client
        """
        hid = emitter.connect(signal, handler, *args)
        self.__signal_handlers.append((emitter, hid))
        return hid

    def bind_after(self, emitter, signal, handler, *args):
        """
        use this function to connect outside of your plugin, for example
        to a client
        """
        hid = emitter.connect_after(signal, handler, *args)
        self.__signal_handlers.append((emitter, hid))
        return hid

    def unbind(self, emitter, handler_id):
        """
        you can use this if you stored your handler_id returned by bind
        to disconnect from the given emitter
        """
        if (emitter, handler_id) in self.__signal_handlers:
            emitter.disconnect(handler_id)
            self.__signal_handlers.remove( (emitter, handler_id) )
            

    # other methods - optional:
    """
    def show_preferences(self, parent) - implement only, if you have preferences to show 
        the parent is the parent for the dialog - usually the preferences window

    def show_about(self, parent) - implement only if you have different dialog
        otherwise the __doc__, __author__, and __license__ are going to be used
        __license__ defaults too LGPLv3 and newer

    gtk Actions should be defined in the __init__ function
    Example
    def __init__(self, control):
        self._actions = [] list
        Plugin.__init__(self, control)
    
    Interfaces
    - similarly to gtk Actions the plugins init should be callod after they
    are defined
    def __init__(self, control):
        self._interfaces = {'foo_functionality': self.interface_function}
    def inferface_function(self, data):
        do_sthing_with_the_data, or return sthing
    
    """



class PluginLayout(gobject.GObject, Plugin):

    #you should overwrite this, if you want other number of things
    slots_count = {
            E_PANEL_TYPE_PLAYLIST: 1,
            E_PANEL_TYPE_EQU: 1,
            E_PANEL_TYPE_EFFECT: 1,
            E_PANEL_TYPE_EFFECT_MISC: 100,
            E_PANEL_TYPE_INFO: 1,
            E_PANEL_TYPE_MEDIALIB: 1,
            E_PANEL_TYPE_COLLECTIONS: 1, 
            E_PANEL_TYPE_OTHER: 100
            }


    def __init__( self, control ):
        self.__gobject_init__()
        Plugin.__init__(self, control)
        self.client = control.get_client()

    def show_main(self, bool = True):
        """
        should be implemented in plugins to show/hide the main window
        """
        raise NotImplementedError

    def toggle_main(self):
        """
        should be implemented in the layout plugin to toggle the 
        visibility of the main window
        """
        raise NotImplementedError

    def get_main_window(self):
        """
        use with care this one. Only if absolutely necessary
        """
        raise NotImplementedError



class PluginPanel(gtk.Frame, Plugin):
    """
    here comes the description
    """

    _type = E_PANEL_TYPE_OTHER
    _title = "The title shown on the window/notebook tab"

    def __init__(self, control):
        gtk.Frame.__init__(self)
        Plugin.__init__(self, control)

    def unload(self):
        Plugin.unload(self)
        self.destroy()


class PluginExtra(gobject.GObject, Plugin):
    """
    Some extra plugins like notifications, status icon, or anything else
    that don't require a panel in the gui
    """

    def __init__(self, control):
        """
        here should come the initialization of the plugin
        connecting to events etc.
        Plugins are strictly event driven, no other function would be called from 
        the outside
        """
        self.__gobject_init__()
        Plugin.__init__(self, control)

